package rov.ui.view;

import rov.ui.WindowManager;
import rov.data.PreferencesManager;
import rov.data.LogManager;
import rov.util.Utils;

import java.awt.*;
import java.awt.event.*;
import java.util.*;
import java.io.*;
import javax.swing.*;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

import java.net.ConnectException;
import java.net.UnknownHostException;
import java.net.NoRouteToHostException;

import net.java.games.input.DirectAndRawInputEnvironmentPlugin;
import net.java.games.input.Component;
import net.java.games.input.Controller;
import net.java.games.input.ControllerEnvironment;
import net.java.games.input.Version;


// displays widgets for controlling motors/servos
public class ControlsView implements IView
{
    private static final String TITLE = "Controls";
    private static final int UPDATE_INTERVAL_MS = 100;
    private static final int CHECK_INTERVAL_MS = 2000;
    private static final String JOYSTICK_THUMB_RIGHT = "Thumb 2";

    private LogManager mLogger;
    private PreferencesManager mPrefs;
    private javax.swing.Timer mUpdater;
    private Container mWindow;
    private JPanel mRoot;
    private JSlider[] mServos;
    private JCheckBox mPump;
    private HashMap<String,JSlider> mAxes;
    private HashMap<String,Integer> mLastVals;
    private HashMap<String,Controller> mControllers;
    private HashMap<String,HashMap<String,net.java.games.input.Component>> mComponents;
    private rov.data.Controller controller;
    private boolean mThumbReady = true;


    public String getTitle()
    {
        return TITLE;
    }

    public void create()
    {
        mLogger = LogManager.getShared();
     	mPrefs = PreferencesManager.getDefaultSimple(false);
        mAxes = new HashMap<String,JSlider>();
        mLastVals = new HashMap<String,Integer>();

        mLastVals.put(PreferencesManager.LEFT_X_DEV, 0);
        mLastVals.put(PreferencesManager.LEFT_Y_DEV, 0);
        mLastVals.put(PreferencesManager.LEFT_TRIGGER_DEV, 0);
        mLastVals.put(PreferencesManager.RIGHT_X_DEV, 0);
        mLastVals.put(PreferencesManager.RIGHT_Y_DEV, 0);
        mLastVals.put(PreferencesManager.RIGHT_TRIGGER_DEV, 0);
        mLastVals.put(JOYSTICK_THUMB_RIGHT, rov.data.Controller.OFF);

        if (mPrefs == null)
        {
            JOptionPane.showMessageDialog(null, "Could not get preferences manager", "Error", JOptionPane.ERROR_MESSAGE);
            return;
        }

        String hints = Utils.run("arp -a");
        String myConfig = Utils.run("ifconfig");
        String guessPattern = myConfig.replaceAll("\n", " ").replaceAll(".*:((?:(?!255)\\d{1,3})\\.\\d{1,3}\\.\\d{1,3}\\.)\\d{1,3}.*", "$1");
        String guessIp = "";

        if (guessPattern.length() <= 15)
        {
System.out.println("searching ip");
            guessIp = hints.replaceAll("\n", " ").replaceAll(".*\\(" + guessPattern + "((?:(?!1\\s)(?!255))\\d{1,3}).*", guessPattern + "$1");
        }

        if (guessIp.length() > 15){System.out.println("no match");
            guessIp = "";}

System.out.println("conf: " + myConfig);
System.out.println("arp: " + hints);
System.out.println("pat: \"" + guessPattern + "\"");
System.out.println("guess: \"" + guessIp + "\"");

        Object addr = JOptionPane.showInputDialog(mWindow, "Enter ROV IP Address. Hints:\n" + hints + "\n", "Network Connection", JOptionPane.QUESTION_MESSAGE, null, null, guessIp);

if (addr == null)
  return;

        try
        {
            controller = new rov.data.Controller(addr.toString());
        }
        catch (IOException e)
        {
            if (!(e instanceof ConnectException || e instanceof UnknownHostException || e instanceof NoRouteToHostException))
                e.printStackTrace();

            JOptionPane.showMessageDialog(null, "Could not connect to given address: \"" + addr.toString() + "\"\nDetails: \"" + e.getMessage() + "\"\nError type: " + e.getClass(), "Network Error", JOptionPane.ERROR_MESSAGE);
            //return;
        }



        mWindow = WindowManager.getInstance().createWindow(TITLE, true, true, true, true, 425, 600);
        mRoot = new JPanel(new BorderLayout());
        mWindow.add(mRoot);

	JPanel west = new JPanel(new BorderLayout());
	JPanel east = new JPanel(new BorderLayout());
	JPanel south = new JPanel(new BorderLayout());

        JSlider slider = new JSlider(SwingConstants.HORIZONTAL, -100, 100, 0);
	slider.setMajorTickSpacing(50);
	slider.setMinorTickSpacing(10);
	slider.setPaintLabels(true);
	slider.setPaintTicks(true);
        slider.addChangeListener(new ChangeListener() {
            public void stateChanged(ChangeEvent e) {
                setGUIOutputs(PreferencesManager.LEFT_X_DEV);
            }
        });
        mAxes.put(PreferencesManager.LEFT_X_DEV, slider);
        west.add(slider, BorderLayout.SOUTH);

        slider = new JSlider(SwingConstants.VERTICAL, -100, 100, 0);
	slider.setMajorTickSpacing(20);
	slider.setMinorTickSpacing(5);
	slider.setPaintLabels(true);
	slider.setPaintTicks(true);
        slider.addChangeListener(new ChangeListener() {
            public void stateChanged(ChangeEvent e) {
                setGUIOutputs(PreferencesManager.LEFT_Y_DEV);
            }
        });
        mAxes.put(PreferencesManager.LEFT_Y_DEV, slider);
        west.add(slider, BorderLayout.CENTER);

        slider = new JSlider(SwingConstants.HORIZONTAL, -100, 100, 0);
	slider.setMajorTickSpacing(50);
	slider.setMinorTickSpacing(10);
	slider.setPaintLabels(true);
	slider.setPaintTicks(true);
        slider.addChangeListener(new ChangeListener() {
            public void stateChanged(ChangeEvent e) {
                setGUIOutputs(PreferencesManager.RIGHT_X_DEV);
            }
        });
        mAxes.put(PreferencesManager.RIGHT_X_DEV, slider);
        east.add(slider, BorderLayout.SOUTH);

        slider = new JSlider(SwingConstants.VERTICAL, -100, 100, 0);
	slider.setMajorTickSpacing(20);
	slider.setMinorTickSpacing(5);
	slider.setPaintLabels(true);
	slider.setPaintTicks(true);
        slider.addChangeListener(new ChangeListener() {
            public void stateChanged(ChangeEvent e) {
                setGUIOutputs(PreferencesManager.RIGHT_Y_DEV);
            }
        });
        mAxes.put(PreferencesManager.RIGHT_Y_DEV, slider);
        east.add(slider, BorderLayout.CENTER);

	JPanel p = new JPanel();
        p.add(mPump = new JCheckBox("Pump"));
	south.add(p, BorderLayout.NORTH);

        final int numServos = 5;
        String[] servoNames = new String[] { "Mast    ", "Camera    ", "Hand    ", "Arm    ", "Fuel Caps    " };
        String[] servoLeftLabels = new String[] { "Hold", "Up", "Open", "Up", "Hold" };
        String[] servoRightLabels = new String[] { "Eject", "Down", "Close", "Down", "Eject" };
        JPanel servos = new JPanel(new GridLayout(numServos, 1));
        mServos = new JSlider[numServos];

        for (int i = 0; i < numServos; i++) {
            JPanel servo = new JPanel(new BorderLayout());
            mServos[i] = new JSlider(SwingConstants.HORIZONTAL, -100, 100, 0);

            servo.setBorder(BorderFactory.createTitledBorder(servoNames[i]));
            servo.add(mServos[i], BorderLayout.CENTER);
            servos.add(servo);

            mServos[i].addChangeListener(new ChangeListener() {
                public void stateChanged(ChangeEvent e) {
                    for (int k = 0; k < mServos.length; k++) {
                        if (mServos[k] == e.getSource()) {
                            int val = mServos[k].getValue() + 100;

                            // random issue sending a 10 here.
                            // maybe because 10 is the newline char?
                            if ((k+7) != 10)
                                setOutput(k + 7, val);
                            else
                                setOutput(12, 200 - val);

                            break;
                        }
                    }
                }
            });

	    mServos[i].setMajorTickSpacing(100);
	    mServos[i].setMinorTickSpacing(10);
	    mServos[i].setPaintLabels(true);
	    mServos[i].setPaintTicks(true);

            // make slider labels human-readable
            Dictionary<Integer,JLabel> labels = mServos[i].getLabelTable();
            Enumeration<Integer> keys = labels.keys();

            while (keys.hasMoreElements())
            {
                int key = keys.nextElement();
                JLabel label = labels.get(key);

                if (key < 0)
                    label.setText(servoLeftLabels[i]);
                else if (key == 0)
                    label.setText("");
                else if (key > 0)
                    label.setText(servoRightLabels[i]);

                label.setSize(label.getText().length() * 10, label.getHeight());
            }
        }

        south.add(servos, BorderLayout.CENTER);
	mRoot.add(west, BorderLayout.WEST);
	mRoot.add(east, BorderLayout.EAST);
	mRoot.add(south, BorderLayout.SOUTH);

        mPump.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                if (mPump.isSelected())
                    setOutput(6, rov.data.Controller.ON);
                else
                    setOutput(6, rov.data.Controller.OFF);
            }
        });

        mUpdater = new javax.swing.Timer(UPDATE_INTERVAL_MS, new ActionListener() {
            private int mNumUpdates;

            public void actionPerformed(ActionEvent e) {
                if (mNumUpdates*UPDATE_INTERVAL_MS > CHECK_INTERVAL_MS) {
                    checkControllers();
                    mNumUpdates = 0;
                }

                Controller c = mControllers.get("sixaxis");

                if (c != null) {
                    c.poll();

                    int val = curveInput((int) (mComponents.get("sixaxis").get("x").getPollData() * 100.0));

                    if (Math.abs(mLastVals.get(PreferencesManager.LEFT_X_DEV) - val) > 5) {
                        setGUIOutputs(PreferencesManager.LEFT_X_DEV, val);
                    }

                    val = (int) (mComponents.get("sixaxis").get("y").getPollData() * -100.0);

                    if (Math.abs(mLastVals.get(PreferencesManager.LEFT_Y_DEV) - val) > 5)
                    {
                        int val2 = (int) (mComponents.get("sixaxis").get("z").getPollData() * 100.0);

                        int left = (curveInput(val) + curveInput(val2)) / 2 + 100;
                        int right = (curveInput(val) + curveInput(val2)) / 2 + 100;

                        setOutputs(PreferencesManager.LEFT_Y_DEV, left, false);
                        setOutputs(PreferencesManager.RIGHT_X_DEV, right, false);

                        mLastVals.put(PreferencesManager.LEFT_Y_DEV, val);
                        mLastVals.put(PreferencesManager.RIGHT_X_DEV, val2);
                    }
                    else
                    {
                        val = (int) (mComponents.get("sixaxis").get("z").getPollData() * 100.0);

                        if (Math.abs(mLastVals.get(PreferencesManager.RIGHT_X_DEV) - val) > 5)
                        {
                            int val2 = (int) (mComponents.get("sixaxis").get("y").getPollData() * -100.0);

                            int left = (curveInput(-val) + curveInput(val2)) / 2 + 100;
                            int right = (curveInput(val) + curveInput(val2)) / 2 + 100;

                            setOutputs(PreferencesManager.LEFT_Y_DEV, left, false);
                            setOutputs(PreferencesManager.RIGHT_X_DEV, right, false);

                            mLastVals.put(PreferencesManager.LEFT_Y_DEV, val2);
                            mLastVals.put(PreferencesManager.RIGHT_X_DEV, val);
                        }
                    }

                    /*val = (int) (mComponents.get("sixaxis").get("rz").getPollData() * -100.0);

                    if (Math.abs(mLastVals.get(PreferencesManager.RIGHT_Y_DEV) - val) > 5) {
                        mLastVals.put(PreferencesManager.RIGHT_Y_DEV, val);
                        setOutputs(PreferencesManager.RIGHT_Y_DEV, val + 100, true);
                    } else*/ {
                        val = (int) (mComponents.get("sixaxis").get("slider 8").getPollData() * 100.0);

                        if (val > -95) {
                            int oldVal = mLastVals.get(PreferencesManager.RIGHT_TRIGGER_DEV);

                            oldVal += 5;

                            if (oldVal > 100)
                                oldVal = 100;

                            setOutputs(PreferencesManager.RIGHT_Y_DEV, oldVal + 100, false);
                            mLastVals.put(PreferencesManager.RIGHT_TRIGGER_DEV, oldVal);
                            mAxes.get(PreferencesManager.RIGHT_Y_DEV).setValue(oldVal);
                        } else {
                            val = (int) (mComponents.get("sixaxis").get("slider 9").getPollData() * -100.0);

                            if (val < 95) {
                                int oldVal = mLastVals.get(PreferencesManager.RIGHT_TRIGGER_DEV);

                                oldVal -= 5;

                                if (oldVal < -100)
                                    oldVal = -100;

                                setOutputs(PreferencesManager.RIGHT_Y_DEV, oldVal + 100, false);
                                mLastVals.put(PreferencesManager.RIGHT_TRIGGER_DEV, oldVal);
                                mAxes.get(PreferencesManager.RIGHT_Y_DEV).setValue(oldVal);
                            } else if (mLastVals.get(PreferencesManager.RIGHT_TRIGGER_DEV) != 0) {
                                //setGUIOutputs(PreferencesManager.RIGHT_Y_DEV, PreferencesManager.RIGHT_TRIGGER_DEV, 0);
                            }
                        }
                    }

                    final int cameraDev = 8;

                    val = (int) (mComponents.get("sixaxis").get("Dead").getPollData() * 100.0 + 100);

                    if (val == rov.data.Controller.ON)
                    {
                        // square button pushed
                        final int deg = 140;
                        setOutput(cameraDev, deg);
                        mServos[1].setValue(deg - 100);
                    }
                    else
                    {
                        val = (int) (mComponents.get("sixaxis").get("Unknown").getPollData() * 100.0 + 100);

                        if (val == rov.data.Controller.ON)
                        {
                            // triangle button pushed
                            final int deg = 55;
                            setOutput(cameraDev, deg);
                            mServos[1].setValue(deg - 100);
                        }
                        else
                        {
                            val = (int) (mComponents.get("sixaxis").get("Unknown 1").getPollData() * 100.0 + 100);

                            if (val == rov.data.Controller.ON)
                            {
                                // circle button pushed
                                final int deg = 118;
                                setOutput(cameraDev, deg);
                                mServos[1].setValue(deg - 100);
                            }
                            else
                            {
                                val = (int) (mComponents.get("sixaxis").get("Unknown 2").getPollData() * 100.0 + 100);

                                if (val == rov.data.Controller.ON)
                                {
                                    // x button pushed
                                    final int deg = 180;
                                    setOutput(cameraDev, deg);
                                    mServos[1].setValue(deg - 100);
                                }
                            }
                        }
                    }

                    final int clawDev = 9;

                    val = (int) (mComponents.get("sixaxis").get(JOYSTICK_THUMB_RIGHT).getPollData() * 100.0 + 100);

                    if (val == rov.data.Controller.ON)
                    {
                        // right joystick pushed

                        if (mThumbReady)
                        {
                            mThumbReady = false;

                            if (mServos[2].getValue() < 100)
                                val = 100;
                            else
                                val = -50;

                            setOutput(clawDev, val + 100);
                            mServos[2].setValue(val);
                        }
                    }
                    else
                    {
                        mThumbReady = true;
                    }
                }

                mNumUpdates++;
            }
        });

        checkControllers();
        mUpdater.start();
    }

    private int curveInput(int input)
    {
        int mult = 1;

        //input = input - 100;

        if (input < 0)
        {
            input = -input;
            mult = -1;
        }

        //return mult * (int) Math.exp(input / 21.72);
        //return mult * (int) Math.pow(input / 10.0, 2) + 100;
        return mult * (int) Math.pow(input / 10.0, 2);
    }

    public void destroy() {
        if (mUpdater != null)
            mUpdater.stop();

        WindowManager.getInstance().destroyWindow(mWindow);
    }

    public void pause() {
    }

    public void resume() {
    }

    private void checkControllers() {
        ControllerEnvironment ce = ControllerEnvironment.getDefaultEnvironment();
        //ControllerEnvironment ce = new DirectAndRawInputEnvironmentPlugin();
        Controller[] ca = ce.getControllers();

        if (mControllers != null && !mControllers.isEmpty())
            return;

        mControllers = new HashMap<String,Controller>();
        mComponents = new HashMap<String,HashMap<String,net.java.games.input.Component>>();
        boolean first = true;

        for (Controller c : ca) {
            //System.out.println("found controller: " + c.getName() + ", type: " + c.getType() + ", subcon: " + c.getControllers().length);

            /*for (Controller subC : c.getControllers()) {
                System.out.println("\tsub controller: " + subC.getName());
            }*/

            if (c.getName().contains("PLAYSTATION")) {
                if (first)
                {
                    first = false;
                    continue;
                }

System.out.println("adding sixaxis " + c.getControllers().length + " " + c.getComponents().length);

                mControllers.put("sixaxis", c);

                HashMap<String,net.java.games.input.Component> con = mComponents.get("sixaxis");

                if (con == null) {
                    con = new HashMap<String,net.java.games.input.Component>();
                    mComponents.put("sixaxis", con);
                }

                for (net.java.games.input.Component comp : c.getComponents()) {
                    int num = 0;
                    String name = comp.getName();

                    while (con.containsKey(name))
                    {
                        num++;
                        name = comp.getName() + " " + num;
                    }

//System.out.println("\tcomponent: " + name + ", analog: " + comp.isAnalog() + ", relative: " + comp.isRelative());

                    con.put(name, comp);
                }
            }

            break;
        }
    }


    private void setGUIOutputs(String axis, int val)
    {
        setGUIOutputs(axis, axis, val);
    }


    private void setGUIOutputs(String axis, String lastAxis, int val)
    {
        mLastVals.put(lastAxis, val);
        mAxes.get(axis).setValue(val);
        setGUIOutputs(axis);
    }


    private void setGUIOutputs(String axis)
    {
        int val = mAxes.get(axis).getValue() + 100;
        setOutputs(axis, val, false);
    }


    private void setOutputs(String axis, int val, boolean alternate)
    {
        Set<Integer> devices = mPrefs.getInts(axis, null);

        if (devices != null)
        {
            Iterator<Integer> it = devices.iterator();

            while (it.hasNext())
            {
                int dev = it.next();

                if (dev < 0)
                    setOutput(-dev - 1, val);
                else
                    setOutput(dev - 1, 200 - val);

                if (alternate)
                    val = 200 - val;
            }
        }
    }

    private void setOutput(int index, int output) {
        mLogger.log(LogManager.CMD_LOG, "m\t" + index + "\t" + output);

        if (controller != null)
            controller.setOutput(index, output);
    }
}
